<?php
/**
 * @Author: liuhongbin
 * @Email: 514660094@qq.com
 * @Date: 2019-02-12 16:15:10
 * @Description:
 * @LastEditors: liuhongbin
 * @LastEditTime: xxx
 */

namespace addons\gs_developer\controller\addons;

use addons\gs_developer\model\Addons\AddonsModel;
use addons\gs_developer\model\Addons\VersionModel;
use app\common\controller\AddonsController;
use app\common\model\addon\Addons;
use hiduoke\services\FormBuilder as Form;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use think\App;
use think\facade\Db;
use think\validate\ValidateRule;
use Yurun\SqlDiff\SqlDiff;
use ZipArchive;

class Version extends AddonsController
{
    private VersionModel $model;
    private string $packPath;

    public function __construct(App $app)
    {
        parent::__construct($app);
        $this->model = new VersionModel();
        $this->packPath = $this->app->getRootPath() . 'public/uploads/addons/gs_developer/pack/';
    }

    public function index()
    {
        // 获取列表数据
        $res = $this->model->getPageList(['field' => 'id,aid,create_time,number,title,update_time']);
        return $this->success($res['count'], $res['data']);
    }

    public function delete($id)
    {
        $this->model->where('id', $id)->delete();
        return $this->success('删除成功');
    }

    public function getForm($id = 0, $aid = 0)
    {
        if (!$aid || (new AddonsModel())->where('id', $aid)->count() == 0) {
            return $this->error('应用不存在');
        }
        $rule = array();
        $row = $this->model->findOrEmpty($id);//创建表单
        $form = Form::createForm("/gs_developer/addons.version/save?id=" . $id)->setMethod("POST");
        //新增表单
        $newNumber = $this->getNewVersionNumber($aid);
        $rule[] = Form::hidden('aid', $aid);
        $rule[] = Form::input('title', '版本名称', $row['title'] ?? '')->effect(['help' => '设置版本名称'])->required();
        $rule[] = Form::input('number', '版本号', $row['number'] ?? $newNumber)->required();
        $title = !$id ? "新增" : "编辑";
        $form->setTitle($title)->setRule($rule);
        return $this->success("", Form::getFormData($form));
    }

    public function save($id = 0)
    {
        $row = $this->model->findOrEmpty($id);
        $formData = getFormData();
        $row->save($formData);
        return $this->success("保存成功");
    }

    public function show($vid = 0)
    {
        $version = $this->model->where(['id' => $vid])->find()->append(['addon']);
        $name = $version['addon']['name'];
        $down_path = $this->request->domain() . '/uploads/addons/gs_developer/pack/' . $name . '/' . $version['number'] . '/';
        if ($version['encrypt']) {
            $file_name = $version['encrypt']['file_name'];
            $version['encrypt_url'] = $down_path . 'encrypt/' . $file_name;
        }
        if ($version['pack']) {
            $file_name = $version['pack']['file_name'];
            $version['pack_url'] = $down_path . $file_name;
        }
        return $this->success('', $version);
    }

    /**
     * 获取最新的版本号
     * @return string
     */
    protected function getNewVersionNumber($aid)
    {
        $version = $this->model->where(['aid' => $aid])->order('id', 'desc')->value('number');
        if (!$version) {
            return '1.0.0';
        } else {
            $version = explode('.', $version);
            $version[2] = $version[2] ?? 0;
            $version[2] = $version[2] + 1;
            return implode('.', $version);
        }
    }


    /**
     * 获取当前安装sql
     * @param $vid
     * @return \think\response\Json
     */
    public function getInstallSql($vid)
    {
        $version = $this->model->where(['id' => $vid])->find();
        $install_sql = $version->install_sql;
        if ($install_sql) {
            return $this->success('', $install_sql);
        } else {
            return $this->error('当前没有SQL数据，请同步最新SQL');
        }
    }

    public function getUpgradeSql($vid)
    {
        $version = $this->model->where(['id' => $vid])->find();
        $upgrade_sql = $version->upgrade_sql;
        $upgrade_sql['new'] = $version;
        if (isset($upgrade_sql['old_vid'])) {
            $upgrade_sql['old'] = (new VersionModel())->where(['id' => $upgrade_sql['old_vid']])->find();
        }
        return $this->success('', $upgrade_sql);
    }

    public function saveInstallSql()
    {
        $vid = input('vid', 0);
        $sql = input('sql', '');
        $version = $this->model->where(['id' => $vid])->find();
        $installSql = [
            'sql'     => $sql,
            'version' => $version['number'],
            'time'    => time(),
            'date'    => date('Y-m-d H:i:s'),
            'edit'    => 1
        ];
        $version->install_sql = $installSql;
        $version->save();
        return $this->success('保存成功', $version);
    }

    /**
     * 更新安装sql
     * @param $vid
     * @return \think\response\Json
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function updateInstallSql($vid)
    {
        $version = $this->model->where(['id' => $vid])->find();
        if (!$version) {
            return $this->error('版本不存在');
        }
        // 判断当前是否为最新版本
        $newVid = $this->model->where(['aid' => $version['aid']])->order('id', 'desc')->value('id');
        if ($newVid != $vid) {
            return $this->error('当前版本不是最新版本，无法更新');
        }
        $name = $version->addon['name'] ?? '';
        $sql = (new AddonsModel())->getCreateTableSql($name);
        $installSql = [
            'sql'     => $sql,
            'version' => $version['number'],
            'time'    => time(),
            'date'    => date('Y-m-d H:i:s'),
            'edit'    => 0
        ];
        $version->install_sql = $installSql;
        $version->save();
        return $this->success('更新成功');
    }

    /**
     * 版本比较
     */
    public function comparisonSql($old_vid, $new_vid)
    {
        $new = $this->model->where(['id' => $new_vid])->find();
        $old = $this->model->where(['id' => $old_vid])->find();
        $sqls = SqlDiff::diff($old['install_sql']['sql'] ?? '', $new['install_sql']['sql'] ?? '');
        $sql = '';
        foreach ($sqls as $key => $value) {
            $sql .= $value . ';

';
        }
        $new->upgrade_sql = array(
            'sql'     => $sql,
            'old_vid' => $old_vid,
            'time'    => time(),
            'date'    => date('Y-m-d H:i:s'),
        );
        $new->save();
        return $this->success('', ['sql' => $sql, 'old' => $old, 'new' => $new]);
    }

    /**
     * 合并更新sql
     */
    public function mergeUpgradeSql($vid)
    {
        $version = $this->model->where(['id' => $vid])->find()->append(['addon']);
        $upgrade_sql = $version->upgrade_sql;
        $default_upgrade_sql = $version['addon']['upgrade_sql'] ?? '';
        if ($default_upgrade_sql) {
            $upgrade_sql['sql'] = $default_upgrade_sql . "\n" . $upgrade_sql['sql'];
            $upgrade_sql['merge'] = 1;
        }
        $version->upgrade_sql = $upgrade_sql;
        $version->save();
        return $this->success('更新成功', $upgrade_sql, $default_upgrade_sql);
    }

    public function saveUpgradeSql()
    {
        $vid = input('vid', 0);
        $sql = input('sql', '');
        $version = $this->model->where(['id' => $vid])->find();
        $upgrade_sql = $version->upgrade_sql;
        $upgrade_sql['sql'] = $sql;
        $upgrade_sql['edit'] = 1;
        $version->upgrade_sql = $upgrade_sql;
        $version->save();
        return $this->success('保存成功', $upgrade_sql);
    }

    /**
     * 版本打包
     * @param $vid
     */
    public function pack($vid)
    {
        // 获取版本信息
        $version = $this->model->where(['id' => $vid])->append(['addon'])->find();
        if (!$version) {
            return $this->error('版本不存在');
        }
        $name = $version->addon['name'] ?? '';
        // 当前应用的目录
        $path = ADDON_PATH . $name . '/';
        // 当前应用资源目录
        $assets = $this->app->getRootPath() . 'public/assets/addons/' . $name . '/';
        $version_path = $this->app->getRootPath() . 'public/uploads/addons/gs_developer/pack/' . $name . '/' . $version['number'] . '/';
        // 打包目录
        $pack_path = $version_path . $name;
        rm_dirs($version_path);
        // 复制文件到打包目录
        copy_dirs($path, $pack_path);
        copy_dirs($assets, $pack_path . '/assets');
        // 复制文件到打包目录
        // 写入install.sql
        $install_sql = $version->install_sql;
        $install_sql = $install_sql['sql'] ?? '';
        file_put_contents($pack_path . '/install.sql', $install_sql);
        // 写入upgrade.sql
        $upgrade_sql = $version->upgrade_sql;
        $upgrade_sql = $upgrade_sql['sql'] ?? '';
        $upgrade_sql = "# " . $version['number'] . "\n" . $upgrade_sql;
        $formData = input('formData', []);
        // 合并更新sql
//        $upgrade_merge = $formData['upgrade_merge'] ?? 0;
//        if ($upgrade_merge) {
//            // TODO 默认更新sql内容
//            $default_upgrade_sql = $version['addon']['upgrade_sql'] ?? '';
//            if ($default_upgrade_sql) {
//                $upgrade_sql = $default_upgrade_sql . "\n" . $upgrade_sql;
//            }
//            if ($upgrade_merge == 2) {
//                // 更新默认的
//                $version['addon']->upgrade_sql = $upgrade_sql;
//                $version['addon']->save();
//            }
//        }
        file_put_contents($pack_path . '/upgrade.sql', $upgrade_sql);
        // 正则修改Plugin.php
        $this->model->replacePlugin($pack_path . '/Plugin.php', $version);

        $file_name = $name . '_' . $version['number'] . '_' . date('Y-m-d_H:i:s') . '.zip';
        $zip_filename = $version_path . $file_name; // 压缩包存放路径与名称
        $this->Zip($version_path, $zip_filename);
        $version->pack = [
            'file_name' => $file_name,
            'time'      => time(),
            'date'      => date('Y-m-d_H:i:s'),
        ];
        $version->save();
        return $this->success('打包成功', [
            'pack' => $version->pack,
            'url'  => $this->request->domain() . '/uploads/addons/gs_developer/pack/' . $name . '/' . $version['number'] . '/' . $file_name
        ]);
    }

    /**
     * 获取文件夹内文件列表
     * @param $vid
     * @return \think\response\Json
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function getFiles($vid)
    {
        $version = $this->model->where(['id' => $vid])->find()->append(['addon']);
        $name = $version['addon']['name'];
        $directory = $this->packPath . $name . '/' . $version['number'] . '/' . $name . '/';
        try {

            $entries = scandir($directory);

            // 过滤掉 "." 和 ".."
            $entries = array_diff($entries, array('..', '.'));

            // 过滤掉非PHP文件
            $entries = array_filter($entries, function ($entry) use ($directory) {
                $fullPath = $directory . '/' . $entry;
                return (is_dir($fullPath) || (is_file($fullPath) && pathinfo($entry, PATHINFO_EXTENSION) === 'php'));
            });

            // 排序，文件夹在前，文件在后，按照a-z顺序
            usort($entries, function ($a, $b) use ($directory) {
                $a = is_dir($directory . '/' . $a) ? '0' . $a : '1' . $a;
                $b = is_dir($directory . '/' . $b) ? '0' . $b : '1' . $b;
                return strcmp(strtolower($a), strtolower($b));
            });
        } catch (\Exception $e) {
            return $this->error("请检查是否未先打包");
        }

        // 将结果转换为JSON格式
        return $this->success('ok', [
            'files'          => $entries,
            'encrypt_config' => $version['addon']['encrypt_config'] ?? [
                    'checkedFiles' => [],
                    'default'      => 0
                ]
        ]);
    }


    /**
     * 初始化加密文件夹
     */
    public function createEncrypt($vid)
    {
        if (!function_exists('exec')) {
            return $this->error("函数exec被禁用，到宝塔-php8.0-禁用函数-删除exec");
        }

        // 在指定目录创建文件夹
        $version = $this->model->where(['id' => $vid])->find()->append(['addon']);
        $addon = (new AddonsModel())->where(['id' => $version['aid']])->find();
        if (!$addon) {
            return $this->error('应用不存在');
        }
        $formData = input('formData', []);
        // 保存默认配置
        if ($formData['default']) {
            $addon->encrypt_config = $formData;
            $addon->save();
        }
        $name = $version['addon']['name'];
        $version_path = $this->packPath . $name . '/' . $version['number'] . '/';
        // 打包目录
        $pack_path = $version_path . $name;
        // 准备被加密后覆盖的目录
        $encrypt_path = $version_path . 'encrypt/' . $name;
        // 删除目录内的内容
        rm_dirs($version_path . 'encrypt/');
        // 复制文件
        copy_dirs($pack_path, $encrypt_path);
        // 选中的目录
        $checkedFiles = $formData['checkedFiles'] ?? ['controller', 'model'];
        // 用户选中准备被加密的目录
        $encrypt_temp_path = $version_path . 'encrypt/' . $name . '_tmp';
        if (!is_dir($encrypt_temp_path)) {
            // 重新创建目录
            mkdir($encrypt_temp_path, 0755, true);
        }
        // 复制pack_path目录中的$checkedFiles文件或者文件夹到encrypt_temp_path
        foreach ($checkedFiles as $checkedFile) {
            $sourcePath = $pack_path . '/' . $checkedFile;
            $destinationPath = $encrypt_temp_path . '/' . $checkedFile;

            if (is_dir($sourcePath)) {
                // 如果是目录，递归复制整个目录
                copy_dirs($sourcePath, $destinationPath);
            } elseif (is_file($sourcePath)) {
                // 如果是文件，直接复制文件
                copy($sourcePath, $destinationPath);
            }
        }

        /************************************************文件加密**************************************************/

        // 压缩目录准备提交加密
        $this->Zip($encrypt_temp_path, $version_path . 'encrypt/' . $name . '_tmp.zip');

        $url = $this->request->domain() . '/uploads/addons/gs_developer/pack/' . $name . '/' . $version['number'] . '/encrypt/' . $name . '_tmp.zip';
        $res = httpPost('http://swoole.dkyx.cc/encrypt', [
            'zip_url'       => $url,
            'php_version'   => '80',
            'save_comments' => '0',
            'zip_type'      => 'url'
        ]);
        $res = json_decode($res, true) ?? false;
        if ($res && $res['code'] == 0 && $res['msg'] == 'success') {
            // 获取下载地址
            $down_url = "http://swoole.dkyx.cc/tmp_down?down_file=" . $res['data']['path'];

            $encrypt_down_path = $version_path . 'encrypt/down';

            // 获取下载文件的内容
            $file_content = file_get_contents($down_url);

            // 检查下载目录是否存在，如果不存在则创建
            if (!file_exists($encrypt_down_path)) {
                mkdir($encrypt_down_path, 0777, true);
            }
            // 保存下载的文件到指定路径
            $local_file_path = $encrypt_down_path . '/' . basename($down_url); // 使用下载链接的文件名
            // 下载文件
            file_put_contents($local_file_path, $file_content);

            /**************************************解压****************************************/
            // 解压目录
            $unzip_path = $encrypt_down_path . "/unzip/";

            if (!is_dir($unzip_path)) {
                // 重新创建目录
                mkdir($unzip_path, 0755, true);
            }
            // 构建解压命令
            $command = "tar -zxvf $local_file_path -C $unzip_path";
            exec($command);
            // 获取unzip/目录中的第一个目录
            $encrypt_path = scandir($unzip_path)[2] ?? false;
            if (!$encrypt_path) {
                return $this->error('解压出错');
            }
            // 覆盖掉需要背加密文件覆盖的目录
            copy_dirs($unzip_path . $encrypt_path, $version_path . "encrypt/" . $name);
            // 加密文件的名称
            $file_name = $name . '_' . $version['number'] . '_encrypt_' . date('Y-m-d_H:i:s') . '.zip';
            // 加密压缩包路径
            $zip_filename = $version_path . 'encrypt/' . $file_name;

            rm_dirs($version_path . 'encrypt/' . $name . '_tmp');
            unlink($version_path . 'encrypt/' . $name . '_tmp.zip');
            rm_dirs($version_path . 'encrypt/down');

            /// 压缩加密文件夹
            $this->Zip($version_path . "encrypt", $zip_filename);
            // 保存加密文件夹信息
            $version->encrypt = [
                'file_name' => $file_name,
                'time'      => time(),
                'date'      => date('Y-m-d_H:i:s'),
            ];
            $version->save();
            return $this->success('加密成功', [
                'encrypt' => $version->pack,
                'url'     => $this->request->domain() . '/uploads/addons/gs_developer/pack/' . $name . '/' . $version['number'] . '/encrypt/' . $file_name
            ]);
        }
        return $this->error('加密失败', $res);
    }

    /**
     * 发布
     */
    public function publish($vid)
    {
        $addon_config = getAddonConfig();
        $token = $addon_config['token'] ?? '';
        $dev_id = $addon_config['dev_id'] ?? '';
        if (!$token) {
            return $this->error('未配置通讯秘钥，请到后台-应用管理-怪兽开发者工具-配置');
        }
        $formData = input('formData', []);
        $version = $this->model->where(['id' => $vid])->find()->append(['addon']);
        if (!$version) {
            return $this->error("版本不存在");
        }
        $name = $version['addon']['name'];
        if ($formData['pack_type'] == 1) {
            // 原始包
            $file_name = $version['pack']['file_name'];
            $url = $this->request->domain() . '/uploads/addons/gs_developer/pack/' . $name . '/' . $version['number'] . '/' . $file_name;
        } else if ($formData['pack_type'] == 2) {
            // 加密包
            $file_name = $version['encrypt']['file_name'];
            $url = $this->request->domain() . '/uploads/addons/gs_developer/pack/' . $name . '/' . $version['number'] . '/encrypt/' . $file_name;
        } else {
            $url = '';
        }
        $data = array(
            'dev_id'     => $dev_id,
            'token'      => $token,
            "version"    => $version['number'],
            'addon_name' => $version['addon']['name'],
            'name'       => $formData['name'],
            'content'    => $formData['content'],
        );
        if ($url) {
            $data['down_url'] = $url;
        }
        $res = $this->httpPost(config('hiduoke.api_app', false).'/developer.cloud/updateVersion', $data);
        $res = json_decode($res, true) ?? false;
        if ($res && $res['code'] == 0) {
            $version->publish = $formData;
            $version->save();
            return $this->success($res['msg']);
        } else {
            return $this->error($res['msg'] ?? "未知错误，请检查应用");
        }
    }

    /** 递归打包文件夹
     * @param $source
     * @param $destination
     * @return bool
     */
    public function Zip($source, $destination)
    {
        if (!extension_loaded('zip') || !file_exists($source)) {
            return false;
        }
        $zip = new ZipArchive();
        if (!$zip->open($destination, ZipArchive::CREATE)) {
            return false;
        }
        $source = str_replace('\\', '/', realpath($source));
        if (is_dir($source) === true) {
            $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
            foreach ($files as $file) {
                $file = str_replace('\\', '/', $file);
                // Ignore "." and ".." folders
                if (in_array(substr($file, strrpos($file, '/') + 1), array('.', '..')))
                    continue;

                $file = realpath($file);

                if (is_dir($file) === true) {
                    // TODO 如果你发现Win环境无法打包zip，恭喜你，帮我修复一下，求求你了。
                    $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
                } else if (is_file($file) === true) {
                    $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
                }
            }
        } else if (is_file($source) === true) {
            $zip->addFromString(basename($source), file_get_contents($source));
        }

        return $zip->close();
    }

    /**
     * curl post请求
     * @param $url string 请求地址
     * @param $data array 请求数据
     * @param $json bool 是否json格式
     * @return bool|string
     */
    private function httpPost($url, $data, $json = false)
    {
        // 模拟提交数据函数
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
//    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在
        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT'] ?? ''); // 模拟用户使用的浏览器
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
        if (!$json) {
            curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
            curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // Post提交的数据包
        } else {
            if (!empty($data)) {
                if (is_array($data)) {
                    $data = json_encode($data);
                }
                curl_setopt($curl, CURLOPT_POST, 1);
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);

                // 发送JSON数据
                curl_setopt($curl, CURLOPT_HEADER, 0);
                curl_setopt($curl, CURLOPT_HTTPHEADER,
                    array(
                        'Content-Type: application/json; charset=utf-8',
                        'Content-Length:' . strlen($data)
                    ));
            }
        }
//    curl_setopt($curl, CURLOPT_COOKIEFILE, $GLOBALS['cookie_file']); // 读取上面所储存的Cookie信息
        curl_setopt($curl, CURLOPT_TIMEOUT, 120); // 设置超时限制防止死循环
        curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
        $response = curl_exec($curl); // 执行操作
        if (curl_errno($curl)) {
            echo 'Errno' . curl_error($curl);
            return false;
        }
        curl_close($curl); // 关键CURL会话
        return $response; // 返回数据
    }
}
